/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// r_main.c

#include "quakedef.h"

#define MAX_ALPHA_OBJECTS	65536

typedef struct alphaobject_s
{
	float dist;
	xcommand_t BeginFunc;
	alphaobjectdrawfunc_t DrawFunc;
	xcommand_t EndFunc;
	void *object;
} alphaobject_t;


alphaobject_t r_alphaobjects[MAX_ALPHA_OBJECTS];
int r_numalphaobjects = 0;


// generic funcs for when one isn't supplied
void R_BeginGenericAlphaObject (void) {}
void R_DrawGenericAlphaObject (void *nothing) {}
void R_EndGenericAlphaObject (void) {}


void R_AddAlphaObject (void *object, float *origin, xcommand_t BeginFunc, alphaobjectdrawfunc_t DrawFunc, xcommand_t EndFunc)
{
	if (r_numalphaobjects < MAX_ALPHA_OBJECTS)
	{
		if (origin)
		{
			// no need to sqrt these as all we're concerned about is relative distances
			// (if x < y then sqrt (x) is also < sqrt (y))
			r_alphaobjects[r_numalphaobjects].dist =
				(origin[0] - ref.ViewOrigin[0]) * (origin[0] - ref.ViewOrigin[0]) +
				(origin[1] - ref.ViewOrigin[1]) * (origin[1] - ref.ViewOrigin[1]) +
				(origin[2] - ref.ViewOrigin[2]) * (origin[2] - ref.ViewOrigin[2]);
		}
		else r_alphaobjects[r_numalphaobjects].dist = 0;

		if (BeginFunc)
			r_alphaobjects[r_numalphaobjects].BeginFunc = BeginFunc;
		else r_alphaobjects[r_numalphaobjects].BeginFunc = R_BeginGenericAlphaObject;

		if (DrawFunc)
			r_alphaobjects[r_numalphaobjects].DrawFunc = DrawFunc;
		else r_alphaobjects[r_numalphaobjects].DrawFunc = R_DrawGenericAlphaObject;

		if (EndFunc && object)
			r_alphaobjects[r_numalphaobjects].EndFunc = EndFunc;
		else r_alphaobjects[r_numalphaobjects].EndFunc = R_EndGenericAlphaObject;

		r_alphaobjects[r_numalphaobjects].object = object;
		r_numalphaobjects++;
	}
}


int R_AlphaSortFunc (alphaobject_t *a, alphaobject_t *b)
{
	// sort back to front
	return (int) (b->dist - a->dist);
}


void R_DrawAlphaObjects (void)
{
	alphaobject_t *lastobject = NULL;

	if (!r_numalphaobjects) return;

	// setup state for alpha objects
	d3d_State->OMSetBlendState (d3d_BlendAlpha);
	d3d_State->OMSetDepthState (d3d_ZmodeNowrite);

	// sort back to front
	if (r_numalphaobjects > 1)
		qsort (r_alphaobjects, r_numalphaobjects, sizeof (alphaobject_t), (sortfunc_t) R_AlphaSortFunc);

	for (int i = 0; i < r_numalphaobjects; i++)
	{
		// check for an object type change
		if (lastobject)
		{
			if (lastobject->BeginFunc != r_alphaobjects[i].BeginFunc ||
				lastobject->DrawFunc != r_alphaobjects[i].DrawFunc ||
				lastobject->EndFunc != r_alphaobjects[i].EndFunc)
			{
				// switch state for the new object
				lastobject->EndFunc ();
				r_alphaobjects[i].BeginFunc ();
			}
		}
		else
		{
			// just beginning the first object
			r_alphaobjects[i].BeginFunc ();
		}

		// and draw this object
		r_alphaobjects[i].DrawFunc (r_alphaobjects[i].object);
		lastobject = &r_alphaobjects[i];
	}

	// call the end function for the last object
	if (lastobject) lastobject->EndFunc ();

	r_numalphaobjects = 0;
}


// alpha surfaces for r_wateralpha < 1 - this is a little messy cos it was hacked on to the existing code
typedef struct alphasurface_s
{
	entity_t *ent;
	msurface_t *surf;
	float alpha;
} alphasurface_t;


#define MAX_ALPHA_SURFACES	65536

alphasurface_t *r_alphasurfaces;
int r_numalphasurfaces = 0;


alphasurface_t *r_lastalphasurface;
msurface_t *r_alphachain;
int r_numalphaindexes;

void R_BeginAlphaSurfaces (void)
{
	/*
	// set up the rendering mode for alpha surfaces
	d3d_State->SetRenderState (&rs_alphablend);
	d3d_State->PSSetSampler (0, d3d_MinMagFilter, d3d_MipFilter, D3DTADDRESS_WRAP, d3d_Anisotropy);

	r_lastalphasurface = NULL;
	r_alphachain = NULL;
	r_numalphaindexes = 0;
	*/
}


void R_SetWaterAlphaState (msurface_t *surf);
void R_SetSolidAlphaState (msurface_t *surf);

void R_DrawAlphaChain (void)
{
	/*
	if (r_alphachain)
	{
		if (r_alphachain->flags & SURF_DRAWTURB)
			R_SetWaterAlphaState (r_alphachain);
		else R_SetSolidAlphaState (r_alphachain);

		R_DrawTextureChain (r_alphachain, r_numalphaindexes);

		r_alphachain = NULL;
		r_numalphaindexes = 0;
	}
	*/
}


void R_EndAlphaSurfaces (void)
{
	// draw anything left over
	R_DrawAlphaChain ();

	// any state takedown specific to alpha surfaces can go here
}


BOOL R_TestNewAlphaChain (alphasurface_t *alphasurf)
{
	// beginning the first chain
	if (!r_lastalphasurface) return TRUE;

	// if any of these change we must begin a new chain
	if (r_lastalphasurface->ent != alphasurf->ent) return TRUE;
	if (r_lastalphasurface->alpha != alphasurf->alpha) return TRUE;
	if (r_lastalphasurface->surf->texinfo->texture != alphasurf->surf->texinfo->texture) return TRUE;
	if (r_lastalphasurface->surf->flags != alphasurf->surf->flags) return TRUE;

	// not changed
	return FALSE;
}


void R_DrawAlphaSurface (alphasurface_t *alphasurf)
{
	/*
	msurface_t *surf = alphasurf->surf;
	entity_t *ent = alphasurf->ent;

	if (R_TestNewAlphaChain (alphasurf))
	{
		// finish the previous and start a new
		R_DrawAlphaChain ();
		R_SetupSurfaceDrawState (ent->model, ent->localmatrix, alphasurf->alpha);
	}

	// add to existing chain
	surf->TextureChain = r_alphachain;
	r_alphachain = surf;
	r_numalphaindexes += surf->numindexes;

	// save back
	r_lastalphasurface = alphasurf;
	*/
}


void R_AddAlphaSurface (entity_t *ent, msurface_t *surf, float alpha)
{
	if (r_numalphasurfaces < MAX_ALPHA_SURFACES)
	{
		// get the midpoint of the surface and transform it to world space for the entity
		// this may be identity if the entity is the world
		float transformed[3];

		ent->localmatrix.Transform (transformed, surf->mids);

		// and now store it out
		r_alphasurfaces[r_numalphasurfaces].ent = ent;
		r_alphasurfaces[r_numalphasurfaces].surf = surf;
		r_alphasurfaces[r_numalphasurfaces].alpha = alpha;

		R_AddAlphaObject (
			&r_alphasurfaces[r_numalphasurfaces],
			transformed,
			R_BeginAlphaSurfaces,
			(alphaobjectdrawfunc_t) R_DrawAlphaSurface,
			R_EndAlphaSurfaces
		);

		r_numalphasurfaces++;
	}
}


void R_NewAlphaSurfaces (void)
{
	// alloc some scratch mem for alpha surfaces
	r_alphasurfaces = (alphasurface_t *) qScratch->FastAlloc (MAX_ALPHA_SURFACES * sizeof (alphasurface_t));

	// no alpha surfaces yet
	r_numalphasurfaces = 0;
}

